package cc.rengu.oltp.utility.util;

import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import java.io.*;
import java.security.*;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;

/**
 * RSA操作工具类
 * Created by 王成 on 2018/5/18.
 */
public class RSAUtil {
    public static final String CHARSET = "UTF-8";
    public static final String RSA_ALGORITHM = "RSA";
    public static final String SIGN_ALGORITHM_SHA256RSA = "SHA256withRSA";

    /**
     * 生成RSA Base64加密的公私钥对
     *
     * @param keySize 密钥长度
     * @return Base64加密的公私钥对
     */
    public static Map<String, String> generateRsaBase64KeyPair(int keySize) {
        try {
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
            /* 初始化KeyPairGenerator对象,密钥长度 */
            keyPairGenerator.initialize(keySize);
            /* 生成公私钥对 */
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            /* 获取公钥 */
            Key publicKey = keyPair.getPublic();
            String publicKeyString = Base64.encodeBase64String(publicKey.getEncoded());
            /* 获取私钥 */
            Key privateKey = keyPair.getPrivate();
            String privateKeyString = Base64.encodeBase64String(privateKey.getEncoded());
            /* 将公私钥对设置到MAP */
            Map<String, String> keyPairMap = new HashMap<>();
            keyPairMap.put("publicKey", publicKeyString);
            keyPairMap.put("privateKey", privateKeyString);
            return keyPairMap;
        } catch (NoSuchAlgorithmException e) {

            return null;
        }
    }

    /**
     * 从Base64加密串中获取RSA公钥
     *
     * @param publicKey Base64加密的公钥字符串
     * @return 公钥信息
     * @throws Exception 异常
     */
    public static RSAPublicKey getPublicKeyFromBase64String(String publicKey) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
        return (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
    }

    /**
     * 从公钥证书文件中获取公钥
     *
     * @param certFilePath 公钥证书文件路径
     * @return 公钥证书
     * @throws Exception 异常
     */
    public static RSAPublicKey getPublicKeyFromCertFile(String certFilePath) throws Exception {
        File certFile = new File(certFilePath);
        if (certFile.exists()) {
            InputStream inputStream = null;
            BufferedReader bufferedReader = null;
            try {
                if (certFilePath.endsWith(".cer")) {
                    inputStream = new FileInputStream(certFilePath);
                    CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
                    X509Certificate x509Certificate = (X509Certificate) certificateFactory.generateCertificate(inputStream);
                    inputStream.close();
                    return (RSAPublicKey) x509Certificate.getPublicKey();
                } else if (certFilePath.endsWith(".pem")) {
                    bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(certFilePath)));
                    StringBuilder stringBuilder = new StringBuilder();
                    String readLine;
                    while ((readLine = bufferedReader.readLine()) != null) {
                        if (readLine.charAt(0) == '-') {
                        } else {
                            stringBuilder.append(readLine);
                        }
                    }
                    return getPublicKeyFromBase64String(stringBuilder.toString());
                } else {
                    return null;
                }
            } catch (Exception e) {
                throw e;
            } finally {
                if (null != inputStream) {
                    inputStream.close();
                }
                if (null != bufferedReader) {
                    bufferedReader.close();
                }
            }
        } else {

            return null;
        }
    }

    /**
     * 从Pfx证书文件名获取别名信息
     *
     * @param certFilePath 证书文件路径
     * @param password     证书密码
     * @return 证书别名集合
     * @throws Exception 异常
     */
    public static List<String> getCertAliasFromPfxCertFile(String certFilePath, String password) throws Exception {
        int index = 0;
        List<String> aliasList = new ArrayList<>();
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        try (FileInputStream fileInputStream = new FileInputStream(certFilePath)) {
            if ((null == password) || "".equals(password.trim())) {
                keyStore.load(fileInputStream, null);
            } else {
                keyStore.load(fileInputStream, password.toCharArray());
            }
        } catch (Exception e) {
            throw e;
        }
        Enumeration list = keyStore.aliases();
        if (list.hasMoreElements()) {
            aliasList.add(index++, (String) list.nextElement());
        }
        return aliasList;
    }

    /**
     * 从PFX证书文件中获取公钥信息
     *
     * @param certFilePath 公钥证书文件路径
     * @param password     证书密码
     * @param alias        证书别名
     * @return 公钥信息
     * @throws Exception 异常
     */
    public static RSAPublicKey getPublicKeyFromPfxCertFile(String certFilePath, String alias, String password) throws Exception {
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(certFilePath);
            if ((null == password) || "".equals(password.trim())) {
                keyStore.load(fileInputStream, null);
            } else {
                keyStore.load(fileInputStream, password.toCharArray());
            }
        } catch (Exception e) {
            throw e;
        } finally {
            if (null != fileInputStream) {
                fileInputStream.close();
            }
        }

        return (RSAPublicKey) keyStore.getCertificate(alias).getPublicKey();
    }

    /**
     * 从PFX证书文件中获取私钥信息
     *
     * @param certFilePath 公钥证书文件路径
     * @param alias        证书别名
     * @param password     证书密码
     * @return 私钥信息
     * @throws Exception 异常
     */
    public static RSAPrivateKey getPrivateKeyFromPfxCertFile(String certFilePath, String alias, String password) throws Exception {
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(certFilePath);
            if ((null == password) || "".equals(password.trim())) {
                keyStore.load(fileInputStream, null);
            } else {
                keyStore.load(fileInputStream, password.toCharArray());
            }
        } catch (Exception e) {
            throw e;
        } finally {
            if (null != fileInputStream) {
                fileInputStream.close();
            }
        }
        return (RSAPrivateKey) keyStore.getKey(alias, password.toCharArray());
    }

    /**
     * 从Base64加密的字符串中获取RSA私钥
     *
     * @param privateKey Base64加密的私钥字符串
     * @return 私钥信息
     * @throws Exception 异常
     */
    public static RSAPrivateKey getPrivateKeyFromBase64String(String privateKey) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey));
        return (RSAPrivateKey) keyFactory.generatePrivate(pkcs8KeySpec);
    }

    /**
     * 从证书文件中获取私钥
     *
     * @param certFilePath 证书文件路径
     * @param alias        证书别名
     * @param password     证书密码
     * @return 私钥信息
     * @throws Exception 异常
     */
    public static RSAPrivateKey getPrivateKeyFromCertFile(String certFilePath, String alias, String password) throws Exception {
        File certFile = new File(certFilePath);
        if (certFile.exists()) {
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            try (FileInputStream fileInputStream = new FileInputStream(certFilePath)) {
                keyStore.load(fileInputStream, password.toCharArray());
            } catch (Exception e) {
                e.printStackTrace();
            }
            return (RSAPrivateKey) keyStore.getKey(alias, password.toCharArray());
        } else {
            return null;
        }
    }

    /**
     * 使用私钥对数据进行加签
     *
     * @param signBlock  加签BLOCK
     * @param privateKey 私钥
     * @param algorithm  算法
     * @param charset    字符集
     * @return Base64加密的加签串
     * @throws Exception 异常
     */
    public static String generateSignString(String signBlock, RSAPrivateKey privateKey, String algorithm, String charset) throws Exception {
        Signature signature = Signature.getInstance(algorithm);
        signature.initSign(privateKey);
        if (null == charset || 0 == charset.trim().length()) {
            signature.update(signBlock.getBytes());
            return new String(Base64.encodeBase64(signature.sign()));
        } else {
            signature.update(signBlock.getBytes(charset));
            return new String(Base64.encodeBase64(signature.sign()));
        }
    }

    /**
     * 数字签名校验
     *
     * @param signBlock 加签BLOCK
     * @param singData  Base64加密的签名串
     * @param publicKey 公钥
     * @param algorithm 算法
     * @param charset   字符集
     * @return true-成功，false-失败
     * @throws Exception 异常
     */
    public static boolean verifySignString(String signBlock, String singData, RSAPublicKey publicKey, String algorithm, String charset) throws Exception {
        Signature signature = Signature.getInstance(algorithm);
        signature.initVerify(publicKey);
        if (null == charset || 0 == charset.trim().length()) {
            signature.update(signBlock.getBytes());
            return signature.verify(Base64.decodeBase64(singData));
        } else {
            signature.update(signBlock.getBytes(charset));
            return signature.verify(Base64.decodeBase64(singData.getBytes(charset)));
        }
    }


    /**
     * 使用公钥进行加密
     *
     * @param encryData 需要加密的字符串
     * @param publicKey 公钥
     * @return 加密字符串
     * @throws Exception 异常
     */
    public static String publicKeyEncrypt(String encryData, RSAPublicKey publicKey) throws Exception {
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        cipher.doFinal(encryData.getBytes());
        return Base64.encodeBase64String(cipher.doFinal(encryData.getBytes()));
    }

    public static String publicKeyEncrypt(byte[] encryData, RSAPublicKey publicKey) throws Exception {
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return Base64.encodeBase64String(cipher.doFinal(encryData));
    }

    /**
     * 使用私钥进行加密
     *
     * @param encryData  需要加密的字符串
     * @param privateKey 私钥
     * @return 加密字符串
     * @throws Exception 异常
     */
    public static String privateKeyEncrypt(String encryData, RSAPrivateKey privateKey) throws Exception {
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        cipher.doFinal(encryData.getBytes());
        return Base64.encodeBase64String(cipher.doFinal(encryData.getBytes()));
    }

    /**
     * 使用公钥进行解密
     *
     * @param decryptDta 需要解密的字符串
     * @param publicKey  公钥
     * @return 解密后的字符串
     * @throws Exception 异常
     */
    public static String publicKeyDecrypt(String decryptDta, RSAPublicKey publicKey) throws Exception {
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, publicKey);
        return Base64.encodeBase64String(cipher.doFinal(decryptDta.getBytes()));
    }

    /**
     * 使用私钥进行解密
     *
     * @param decryptDta 需要解密的字符串
     * @param privateKey 私钥
     * @return 解密后的字符串
     * @throws Exception 异常
     */
    public static String privateKeyDecrypt(String decryptDta, RSAPrivateKey privateKey) throws Exception {
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        cipher.doFinal(decryptDta.getBytes());
        return Base64.encodeBase64String(cipher.doFinal(decryptDta.getBytes()));
    }

    /**
     * 使用私钥进行解密
     *
     * @param decryptData 加密数据
     * @param privateKey  私钥
     * @return 解密后的字符串
     * @throws Exception 异常
     */
    public static String privateKeyDecrypt(byte[] decryptData, RSAPrivateKey privateKey) throws Exception {
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return Base64.encodeBase64String(cipher.doFinal(decryptData));
    }


    /**
     * 银联无卡快捷 RSA私钥签名
     * <p>
     * 过程说明： 先对报文中<root></root>标签中的所有内容（包括<root>和</root>标签自身）
     * 生成SHA-256摘要，再对摘要进行RSA签名，最后对生成的签名进行base64编码
     *
     * @param privateKey 签名私钥
     * @param msg        待签名消息
     * @return base64后的签名结果
     */

    public static String rsaSignWithSha256(RSAPrivateKey privateKey, byte[] msg) throws Exception {
        //生成SHA-256摘要
        byte[] shaMsg = SHAUtil.sha256(msg);
        //再对摘要进行RSA签名
        byte[] sign = signWithSha256(privateKey, shaMsg);
        //最后对生成的签名进行base64编码
        return Base64.encodeBase64String(sign);
    }

    /**
     * 银联无卡快捷 RSA Sha256摘要  私钥签名
     *
     * @param privateKey 私钥
     * @param data       消息
     * @return 签名
     */
    public static byte[] signWithSha256(RSAPrivateKey privateKey, byte[] data) throws Exception {
        byte[] result = null;
        Signature st;
        try {
            st = Signature.getInstance(SIGN_ALGORITHM_SHA256RSA);
            st.initSign(privateKey);
            st.update(data);
            result = st.sign();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }


    /**
     * 银联无卡快捷 RSA公钥验签
     * <p>
     * 过程说明：先对报文中<root></root>标签中的所有内容（包括<root>和</root>标签自身）
     * 生成SHA-256摘要，然后将base64编码后的签名数据进行base64解码， 最后使用摘要和base64解码后的签名进行RSA验签
     *
     * @param publicKey 验签公钥
     * @param msg       被签名消息
     * @param signB     待验证签名
     * @return 验签结果：true(验签成功) 或 false(验签失败)
     */
    public static boolean rsaVerifyWithSha256(RSAPublicKey publicKey, byte[] msg, String signB) {
        boolean result;
        //生成SHA-256摘要
        byte[] shaMsg = SHAUtil.sha256(msg);
        //然后将base64编码后的签名数据进行base64解码
        byte[] sign = Base64.decodeBase64(signB);
        //使用摘要和base64解码后的签名进行RSA验签
        result = verifyWithSha256(publicKey, shaMsg, sign);
        return result;
    }


    /**
     * 银联无卡快捷RSA Sha256摘要  公钥验签
     *
     * @param publicKey 公钥
     * @param data      消息
     * @param sign      签名
     * @return 验签结果
     */
    public static boolean verifyWithSha256(RSAPublicKey publicKey, byte[] data, byte[] sign) {
        boolean correct = false;
        try {
            Signature st = Signature.getInstance(SIGN_ALGORITHM_SHA256RSA);
            st.initVerify(publicKey);
            st.update(data);
            correct = st.verify(sign);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return correct;
    }

    /**
     * RSA Ecb模式 公钥加密
     *
     * @param publicKey 公钥
     * @param data      明文
     * @param padMode   填充模式
     * @return 密文
     */
    public static byte[] rsaEcbEncrypt(RSAPublicKey publicKey, byte[] data, String padMode) {
        String algorithm = "RSA/ECB/" + padMode;
        byte[] res = null;
        if (publicKey == null) {
            return null;
        }
        try {
            Cipher cipher = Cipher.getInstance(algorithm);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            res = cipher.doFinal(data);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return res;
    }

    /**
     * RSA Ecb 私钥解密
     *
     * @param privateKey 私钥
     * @param data       密文
     * @param padMode    填充模式
     * @return 明文
     */
    public static byte[] rsaEcbDecrypt(RSAPrivateKey privateKey, byte[] data, String padMode) {
        if (privateKey == null) {
            return null;
        }
        String algorithm = "RSA/ECB/" + padMode;
        byte[] res = null;
        try {
            Cipher cipher = Cipher.getInstance(algorithm);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            res = cipher.doFinal(data);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return res;
    }

    /**
     * RSAES-OAEP 公钥加密
     *
     * @param publicKey 公钥
     * @param data      待加密数据
     * @return 加密后的数据
     */
    public static byte[] rspOaepEncrypt(RSAPublicKey publicKey, byte[] data) {
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            return cipher.doFinal(data);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * RSAES-OAEP 私钥解密
     *
     * @param privateKey 私钥
     * @param data       待解密的数据
     * @return 解密后的数据
     */
    public static byte[] rspOaepDecrypt(RSAPrivateKey privateKey, byte[] data) {
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            return cipher.doFinal(data);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
